/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved.
 * Description: cph netd default settings
 */

#include <unistd.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <fstream>
#include <regex>
#include <sstream>
#include <cstring>
#include <netutils/ifc.h>

#define LOG_TAG "Netd"

#include "cutils/log.h"
#include "cutils/properties.h"
#include "Controllers.h"
#include <android-base/properties.h>
#include <binder/IServiceManager.h>
#include "android/net/IDnsResolver.h"

using android::sp;
using android::IServiceManager;
using android::IBinder;
using android::String16;
using android::net::IDnsResolver;
using android::net::ResolverParamsParcel;
using android::net::gLog;

const int MAX_HWADDR_LEN = 6;
const int MAX_HWADDR_STR_LEN = 18;

struct IcmpEcho {
    // header
    uint8_t type;
    uint8_t code;
    uint16_t checksum;

    uint16_t ident;
    uint16_t seq;

    // data
    uint32_t data;
};

static std::string getGateway(const std::string &interface)
{
    std::ifstream file("/proc/net/route");
    std::string line;
    std::string iface;
    uint32_t dest = 0;
    uint32_t gateway = 0;
    uint32_t flags = 0;
    std::string res;

    if (!file.good()) {
        gLog.error("open /proc/net/route fail");
        return res;
    }

    while (std::getline(file, line)) {
        std::stringstream ss(line);
        ss >> iface >> std::hex >> dest >> std::hex >> gateway >> std::hex >> flags;
        if (iface == interface && (flags & RTF_GATEWAY)) {
            struct sockaddr_in addr{};
            addr.sin_addr.s_addr = gateway;
            res = inet_ntoa(addr.sin_addr);
        }
    }
    return res;
}

// example: shape "fd00000000000000000000000aed0001" to "fd00:0000:0000:0000:0000:0000:0aed:0001"
static std::string shapeV6Addr(const std::string &addr)
{
    const int v6AddrLength = 32;
    if (addr.size() != v6AddrLength) {
        return std::string();
    }
    return addr.substr(0, 4) + ":" +
           addr.substr(4, 4) + ":" +
           addr.substr(8, 4) + ":" +
           addr.substr(12, 4) + ":" +
           addr.substr(16, 4) + ":" +
           addr.substr(20, 4) + ":" +
           addr.substr(24, 4) + ":" +
           addr.substr(28, 4);
}

static std::string getGatewayV6(const std::string &interface)
{
    std::ifstream file("/proc/net/ipv6_route");
    std::string line;
    std::string placeholder;
    std::string gateway;
    std::string iface;
    std::string res;
    uint32_t flags = 0;

    if (!file.good()) {
        return res;
    }

    while (std::getline(file, line)) {
        std::stringstream ss(line);
        ss >> placeholder >> placeholder >> placeholder >> placeholder >> gateway
           >> placeholder >> placeholder >> placeholder >> std::hex >> flags >> iface;
        if (iface == interface && (flags & RTF_GATEWAY)) {
            res = shapeV6Addr(gateway);
        }
    }
    return res;
}

static std::string getCIDR(uint32_t subnetMask)
{
    uint32_t count;
    for (count = 0; subnetMask != 0; subnetMask >>= 1u) {
        count += subnetMask & 1u;
    }
    return std::to_string(count);
}

static std::string getSubnet(const char* interface)
{
    struct sockaddr_in *sin;
    struct ifaddrs *ifa;
    struct ifaddrs *ifList;
    std::string res;

    if (getifaddrs(&ifList) < 0) {
        return res;
    }

    for (ifa = ifList; ifa != nullptr; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr || !ifa->ifa_name) {
            continue;
        }
        if (ifa->ifa_addr->sa_family == AF_INET && strcmp(static_cast<const char*>(ifa->ifa_name), interface) == 0) {
            sin = (struct sockaddr_in *)ifa->ifa_addr;
            uint32_t ip = sin->sin_addr.s_addr;
            sin = (struct sockaddr_in *)ifa->ifa_netmask;
            uint32_t subnetMask = sin->sin_addr.s_addr;
            struct sockaddr_in subnet{};
            subnet.sin_addr.s_addr = ip & subnetMask;
            std::string addr(inet_ntoa(subnet.sin_addr));
            res = addr + "/" + getCIDR(subnetMask);
        }
    }

    freeifaddrs(ifList);

    return res;
}

static uint16_t calculateChecksum(unsigned char* buffer, int bytes)
{
    uint32_t checksum = 0;
    unsigned char* end = buffer + bytes;

    // add bytes add last byte and reset end
    if (bytes % 2 == 1) {
        end = buffer + bytes - 1;
        checksum += (*end) << 8;
    }

    // add words of two bytes, one by one
    while (buffer < end) {
        checksum += buffer[0] << 8;
        checksum += buffer[1];
        buffer += 2;
    }

    // add carry if any
    uint32_t carray = checksum >> 16;
    while (carray) {
        checksum = (checksum & 0xffff) + carray;
        carray = checksum >> 16;
    }

    // negate it
    checksum = ~checksum;

    return checksum & 0xffff;
}

static int sendIcmp(const char* ip)
{
    if (!ip) {
        gLog.error("sendIcmp: ip is null");
        return -1;
    }

    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));

    addr.sin_family = AF_INET;
    addr.sin_port = 0;
    if (inet_aton(ip, (struct in_addr*)&addr.sin_addr.s_addr) == 0) {
        return -1;
    };

    int sock = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMP);

    struct IcmpEcho icmp;
    bzero(&icmp, sizeof(icmp));

    icmp.type = 8;
    icmp.code = 0;
    icmp.ident = htons(getpid());
    icmp.seq = htons(1);
    icmp.data = 0;
    icmp.checksum = htons(calculateChecksum((unsigned char *)&icmp, sizeof(icmp)));

    int bytes = sendto(sock, &icmp, sizeof(icmp), 0, (struct sockaddr*)&addr, sizeof(addr));
    close(sock);

    if (bytes == -1) {
        return -1;
    }

    return 0;
}

static int string2HwAddr(const char *str, uint8_t *hwAddr)
{
    if (hwAddr == nullptr || str == nullptr) {
        return -1;
    }
    char *e = nullptr;
    char *s = (char *)str;
    for (int i = 0; i < MAX_HWADDR_LEN; ++i) {
        hwAddr[i] = s ? strtoul(s, &e, 16) : 0;
        if (s) {
            s = (*e) ? e + 1 : e;
        }
    }
    return 0;
}

static int configHwAddr(const char* iface, bool& changed)
{
    uint8_t hwAddr[MAX_HWADDR_LEN] = {0};
    char hwAddrStr[MAX_HWADDR_STR_LEN] = {0};
    char hwAddrProp[PROPERTY_VALUE_MAX] = {0};
    int count = 0;
    int ret = 0;
    const int maxCount = 500;

    if (iface == nullptr) {
        gLog.error("configHwAddr: iface is null");
        return -1;
    }

    // retrieve hw addr from property, wait max to 5s for property system start
    while (strlen(hwAddrProp) <= 0 && count < maxCount) {
        property_get("ro.hw.address", hwAddrProp, "\0");
        usleep(10000);
        count++;
    }

    if (count == maxCount) {
        gLog.error("configHwAddr: can not read property");
        return -1;
    }

    // retrieve hw addr from current iface
    ifc_init();
    if (ifc_get_hwaddr(iface, hwAddr) < 0) {
        gLog.error("configHwAddr: get current hw address fail");
        ret = -1;
        goto out;
    }
    snprintf(hwAddrStr, MAX_HWADDR_STR_LEN, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
        hwAddr[0], hwAddr[1], hwAddr[2], hwAddr[3], hwAddr[4], hwAddr[5]);

    // case 1, current hw addr same with property, need do nothing (netd restart case)
    if (strncmp(hwAddrProp, hwAddrStr, MAX_HWADDR_STR_LEN - 1) == 0) {
        gLog.info("configHwAddr: current hw address is %s, no need to change", hwAddrStr);
        ret = 0;
        goto out;
    }

    gLog.info("configHwAddr: current hw address is %s, set to %s", hwAddrStr, hwAddrProp);

    // case 2, current hw addr different from property, need to set new hw addr (phone restart case)
    string2HwAddr(hwAddrProp, hwAddr);
    if (ifc_set_hwaddr(iface, hwAddr) < 0) {
        gLog.error("configHwAddr: set hw address fail");
        ret = -1;
        goto out;
    }

    changed = true;

out:
    ifc_close();
    return ret;
}

int setResolverConfiguration(const std::vector<std::string> &servers)
{
    sp<IServiceManager> sm = android::defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("dnsresolver"));
    if (binder == nullptr) {
        gLog.error("Unable to get binder service");
        return -1;
    }
    sp<IDnsResolver> dnsResolver = interface_cast<IDnsResolver>(binder);
    
    ResolverParamsParcel paramsParcel;
    paramsParcel.netId = 100;
    paramsParcel.sampleValiditySeconds = 1800;
    paramsParcel.successThreshold = 25;
    paramsParcel.minSamples = 8;
    paramsParcel.maxSamples = 64;
    paramsParcel.baseTimeoutMsec = 1000;
    paramsParcel.retryCount = 2;
    paramsParcel.servers = servers;

    auto rv = dnsResolver->createNetworkCache(100);
    if (!rv.isOk()) {
        gLog.error("createNetworkCache fail: %s", rv.toString8().c_str());
        return -1;
    }

    rv = dnsResolver->setResolverConfiguration(paramsParcel);
    if (!rv.isOk()) {
        gLog.info("setResolverConfiguration fail: %s", rv.toString8().c_str());
        return -1;
    }

    return 0;
}

bool isValidIpAddress(const std::string& ip) {
    const std::regex ipPattern("^(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]))");
    return std::regex_match(ip, ipPattern);
}

void configDefaultSettings()
{
    using android::net::gCtls;
    int ret = 0;
    int netId = 100;
    int count = 0;
    const char* interface = "wlan0";
    bool hwAddrChanged = false;

    configHwAddr(interface, hwAddrChanged);

    // wait max 5s for dns prop set
    std::vector<std::string> servers;
    std::vector<std::string> dnsKeys = {"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
    while (count < 100) {
        auto it = dnsKeys.begin();
        while (it != dnsKeys.end()) {
            std::string dnsKey = *it;
            std::string dnsValue = android::base::GetProperty(dnsKey, "");
            if (dnsValue.empty()) {
                it++;
                continue;
            }
            it = dnsKeys.erase(it);
            if (isValidIpAddress(dnsValue)) {
                servers.push_back(dnsValue);
                gLog.info("add dns server %s=%s", dnsKey.c_str(), dnsValue.c_str());
            } else {
                gLog.error("ignore invalid dns server %s=%s", dnsKey.c_str(), dnsValue.c_str());
            }
        }
        if (dnsKeys.empty()) {
            break;
        }
        count++;
        usleep(10000);
    }
    if (servers.empty()) {
        // use default dns server
        servers.push_back("8.8.8.8");
    }

    ret = gCtls->netCtrl.createPhysicalNetwork(netId, PERMISSION_NONE, false);
    if (ret != 0) {
        gLog.error("createPhysicalNetwork fail %d", ret);
        return;
    }

    ret = gCtls->netCtrl.addInterfaceToNetwork(netId, interface);
    if (ret != 0) {
        gLog.error("addInterfaceToNetwork fail %d", ret);
        return;
    }

    ret = gCtls->netCtrl.setDefaultNetwork(netId);
    if (ret != 0) {
        gLog.error("setDefaultNetwork fail %d", ret);
        return;
    }

    ret = setResolverConfiguration(servers);
    if (ret != 0) {
        gLog.error("setResolverConfiguration fail %d", ret);
    }

    std::string gateway = getGateway(interface);
    if (gateway.empty()) {
        gLog.error("getGateway fail");
        return;
    }

    ret = gCtls->netCtrl.addRoute(netId, interface, "0.0.0.0/0", gateway.c_str(), false, 0, 0);
    if (ret != 0) {
        gLog.error("addRoute 1 fail %d", ret);
    }
    gLog.info("Add route 0.0.0.0/0 %s", gateway.c_str());

    std::string subnet = getSubnet(interface);
    if (subnet.empty()) {
        gLog.error("getSubnet fail");
        return;
    }

    ret = gCtls->netCtrl.addRoute(netId, interface, subnet.c_str(), nullptr, false, 0, 0);
    if (ret != 0) {
        gLog.error("addRoute 2 fail %d", ret);
    }
    gLog.info("Add route %s", subnet.c_str());

    std::string gatewayV6 = getGatewayV6(interface);

    if (!gatewayV6.empty()) {
        ret = gCtls->netCtrl.addRoute(netId, interface, "::/0", gatewayV6.c_str(), false, 0, 0);
        if (ret == 0) {
            gLog.info("Add route %s", gatewayV6.c_str());
        }
    }

    // if hw address changed, send a ICMP packet to host for updating ARP table
    if (hwAddrChanged) {
        ret = sendIcmp(gateway.c_str());
        gLog.info("Send ICMP packet %s", ret == 0 ? "success" : "fail");
    }
}
